/*
 *******************************************************************
 * Author: xuelei
 * Create Date: 2021-09-09 14:17
 * Description: create file
 *******************************************************************
 */
package com.xuelei.cloud.bc.hmac;

import com.xuelei.cloud.bc.Checker;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

/**
 * 基于HMAC-SM3的消息验证码工具类
 * @author xuelei
 * @date 2021-09-09
 */
public class HmacSM3Util {
    
    private static final Logger log = LoggerFactory.getLogger(HmacSM3Util.class);
    
    private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
    
    /**
     * 根据给定的数据块和密钥计算消息验证码
     * @param dataBlock 待计算消息验证码的数据块
     * @param keyBase16 Base16编码的密钥
     * @return Base16编码的Hmac值
     */
    public static String hmacBase16(byte[] dataBlock, String keyBase16) {
        byte[] hmacValue = doHmac(dataBlock, keyBase16);
        return Hex.toHexString(hmacValue);
    }
    
    /**
     * 根据给定的计算字符串和密钥计算消息验证码
     * @param text 待计算消息验证码的字符串
     * @param keyBase16 Base16编码的密钥
     * @return Base16编码的Hmac值
     */
    public static String hmacBase16(String text, String keyBase16) {
        Checker.checkText(text,"text is empty");
        byte[] dataBlock = text.getBytes(DEFAULT_CHARSET);
        return hmacBase16(dataBlock, keyBase16);
    }
    
    /**
     * 根据给定的数据块和密钥计算消息验证码
     * @param dataBlock 待计算消息验证码的数据块
     * @param keyBase16 Base16编码的密钥
     * @return Base64编码的Hmac值
     */
    public static String hmacBase64(byte[] dataBlock, String keyBase16) {
        byte[] hmacValue = doHmac(dataBlock, keyBase16);
        return Base64.toBase64String(hmacValue);
    }
    
    /**
     * 根据给定的计算字符串和密钥计算消息验证码
     * @param text 待计算消息验证码的字符串
     * @param keyBase16 Base16编码的密钥
     * @return Base64编码的Hmac值
     */
    public static String hmacBase64(String text, String keyBase16) {
        Checker.checkText(text,"text is empty");
        byte[] dataBlock = text.getBytes(DEFAULT_CHARSET);
        return hmacBase64(dataBlock, keyBase16);
    }
    
    /**
     * 校验给定的数据块和密钥算出的消息验证码与给定的消息验证码是否一致
     * @param dataBlock 待计算消息验证码的数据块
     * @param hmacBase16 给定的基于Base16编码的消息验证码
     * @param keyBase16 Base16编码的密钥
     * @return 校验结果
     */
    public static boolean verifyHmacBase16(byte[] dataBlock, String hmacBase16, String keyBase16) {
        return verify(dataBlock, Hex.decode(hmacBase16), keyBase16);
    }
    
    /**
     * 校验给定的字符串和密钥算出的消息验证码与给定的消息验证码是否一致
     * @param text 待计算消息验证码的字符串
     * @param hmacBase16 给定的基于Base16编码的消息验证码
     * @param keyBase16 Base16编码的密钥
     * @return 校验结果
     */
    public static boolean verifyHmacBase16(String text, String hmacBase16, String keyBase16) {
        return verifyHmacBase16(text.getBytes(DEFAULT_CHARSET), hmacBase16, keyBase16);
    }
    
    /**
     * 校验给定的数据块和密钥算出的消息验证码与给定的消息验证码是否一致
     * @param dataBlock 待计算消息验证码的数据块
     * @param hmacBase64 给定的基于Base64编码的消息验证码
     * @param keyBase16 Base16编码的密钥
     * @return 校验结果
     */
    public static boolean verifyHmacBase64(byte[] dataBlock, String hmacBase64, String keyBase16) {
        return verify(dataBlock, Base64.decode(hmacBase64), keyBase16);
    }
    
    /**
     * 校验给定的字符串和密钥算出的消息验证码与给定的消息验证码是否一致
     * @param text 待计算消息验证码的字符串
     * @param hmacBase64 给定的基于Base64编码的消息验证码
     * @param keyBase16 Base16编码的密钥
     * @return 校验结果
     */
    public static boolean verifyHmacBase64(String text, String hmacBase64, String keyBase16) {
        return verifyHmacBase64(text.getBytes(DEFAULT_CHARSET), hmacBase64, keyBase16);
    }
    
    private static byte[] doHmac(byte[] dataBlock, String keyBase16) {
        Checker.checkBlock(dataBlock, "data block is empty");
        Checker.checkText(keyBase16, "keyBase16 is empty");
        KeyParameter keyParameter = new KeyParameter(Hex.decode(keyBase16));
        SM3Digest sm3Digest = new SM3Digest();
        HMac mac = new HMac(sm3Digest);
        mac.init(keyParameter);
        mac.update(dataBlock, 0, dataBlock.length);
        byte[] hmacValue = new byte[mac.getMacSize()];
        mac.doFinal(hmacValue, 0);
        return hmacValue;
    }
    
    private static boolean verify(byte[] dataBlock, byte[] hmacForCompare, String keyBase16) {
        Checker.checkBlock(dataBlock, "data block is empty");
        Checker.checkBlock(hmacForCompare, "hmac is empty");
        Checker.checkText(keyBase16, "keyBase16 is empty");
        byte[] hmacData = doHmac(dataBlock, keyBase16);
        if (log.isDebugEnabled()) {
            log.debug("srcDataHmacBase16: {}", Hex.toHexString(hmacData));
            log.debug("givenHmacBase16: {}", Hex.toHexString(hmacForCompare));
        }
        return Arrays.areEqual(hmacData, hmacForCompare);
    }
}
